#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "NoteHandler.h"
#include "Channel.h"
#include "Effects.h"
#include "Instrument.h"
#include "FNotes.h"
#include "FTables.h"

#define min_virtual_volume 0x200

extern void CITFilter_Params(Note* pNote, uint32_t mixfeq);

void Notes_PreClearAllNotes(NoteHandler* pHNotes)
{
	// Process notes in the primary pool
	Note* pNote = pHNotes->PrimaryNotesPool.pfirst;
	while (pNote)
	{
		pNote->new_volume = 0; // Volume ramp them to 0
		pNote = pNote->pnext;
	}

	// Process notes in the virtual pool
	pNote = pHNotes->VirtualNotesPool.pfirst;
	while (pNote)
	{
		pNote->new_volume = 0; // Volume ramp them to 0
		pNote = pNote->pnext;
	}
}

/*
 * Clear all notes:
 * - Actives notes array is set to empty
 * - Primary notes pool is set to empty
 * - Virtual notes pool is set to empty
 * - Unused notes pool is filled with n notes, n = polyphony
 * - (max - polyphony) notes are unlinked from any pool.
 */
void Notes_ClearAllNotes(NoteHandler* pHNotes)
{
	int count;

	pHNotes->ActiveNotes = 0;
	pHNotes->PrimaryNotesPool.pfirst =
	pHNotes->PrimaryNotesPool.plast =
	pHNotes->VirtualNotesPool.pfirst =
	pHNotes->VirtualNotesPool.plast = NULL;

	//Put x notes in the unused pool,x being the maximal polyphony
	Note* pNote = &pHNotes->NotesArray[0];
	Note* pPrev = NULL;

	pHNotes->UnusedNotesPool.pfirst = pNote;
	for (count = pHNotes->Polyphony; count > 0; count--)
	{
		pNote->pprevious = pPrev;
		pNote->pnext = pNote + 1;
		pNote->new_volume = 0; // Volume ramp them to 0
		pPrev = pNote;
		pNote = pNote->pnext;
	}
	pPrev->pnext = NULL;
	pHNotes->UnusedNotesPool.plast = pPrev;

	// Ensure that the other notes are cleaned
	for (count = hnotes_max_notes - pHNotes->Polyphony; count > 0; count--)
	{
		pNote->pprevious =
		pNote->pnext = NULL;
		pNote->new_volume = 0; // Volume ramp them to 0
		pNote++;
	}
}

static void NoteList_RemoveNote(NoteList* pList, Note* pNote)
{
	Note* pPrev = pNote->pprevious;
	Note* pNext = pNote->pnext;

	if (pPrev)
		pPrev->pnext = pNext;
	else
		pList->pfirst = pNext;

	if (pNext)
		pNext->pprevious = pPrev;
	else
		pList->plast = pPrev;
}

static void NoteList_InsertNote(NoteList* pList, Note* pNote)
{
	Note* pNext = pList->pfirst;

	pNote->pprevious = NULL;
	pNote->pnext = pNext;
	pList->pfirst = pNote;

	if (pNext)
		pNext->pprevious = pNote;
	else
		pList->plast = pNote;
}

void Notes_SetPolyphony(NoteHandler* pHNotes, uint32_t polyphony)
{
	int i, j;
	Note* pNote;

	if (polyphony > pHNotes->Polyphony)
	{
		// Add absent notes to free pool
		for ( i = pHNotes->Polyphony, j = 0
		    ; (j < hnotes_max_notes) && (i < polyphony)
		    ; j++)
		{
			pNote = &pHNotes->NotesArray[j];

			if ((pNote->pprevious == NULL)
			&&  (pNote->pnext== NULL)
			&&  (pHNotes->UnusedNotesPool.pfirst != pNote)
			&&  (pHNotes->VirtualNotesPool.pfirst != pNote)
			&&  (pHNotes->PrimaryNotesPool.pfirst != pNote))
			{
				NoteList_InsertNote(&pHNotes->UnusedNotesPool, pNote);
				i++;
			}
		}

		pHNotes->Polyphony = i;
	}
	else if (polyphony < pHNotes->Polyphony)
	{
		NoteList* pPool;

		i = pHNotes->Polyphony;

		// Remove notes from pools
		pPool = &pHNotes->UnusedNotesPool;
		while (pPool->pfirst && (i > polyphony))
		{
			pNote = pPool->pfirst;
			NoteList_RemoveNote(pPool, pNote);
			pNote->pprevious = NULL;
			pNote->pnext = NULL;
			i--;
		}

		pPool = &pHNotes->VirtualNotesPool;
		while (pPool->pfirst && (i > polyphony))
		{
			pNote = pPool->pfirst;
			NoteList_RemoveNote(pPool, pNote);
			pNote->pprevious = NULL;
			pNote->pnext = NULL;
			i--;
		}

		pPool = &pHNotes->PrimaryNotesPool;
		while (pPool->pfirst && (i > polyphony))
		{
			pNote = pPool->pfirst;
			NoteList_RemoveNote(pPool, pNote);
			pNote->pchannel->pnote = NULL;
			pNote->pprevious = NULL;
			pNote->pnext = NULL;
			i--;
		}

		pHNotes->Polyphony = i;
	}
}

void Notes_PreActOnDuplicateNotes
	( NoteHandler* pHNotes
	, uint8_t      action
	, Channel*     pChannel
	, uint8_t      inst_note
	, uint8_t      inst_id
	, uint8_t      smp_id
	, uint8_t      check_type
	)
{
	// loop on virtual channels
	Note* pNote = pHNotes->VirtualNotesPool.pfirst;
	if (!pNote) return;

	switch(check_type)
	{
		case Inst_DCT_note:
		{
			do
			{
				if ((pNote->pchannel == pChannel)
				&&  (pNote->inst_note == inst_note)
				&&  (pNote->inst_id == inst_id))
					Note_PreApplyNoteAction(pNote, action);

				pNote = pNote->pnext;
			}
			while (pNote);
		}
		break;
		case Inst_DCT_sample:
		{
			do
			{
				if ((pNote->pchannel == pChannel)
				&&  (pNote->smp_id == smp_id))
					Note_PreApplyNoteAction(pNote, action);

				pNote = pNote->pnext;
			}
			while (pNote);
		}
		break;
		case Inst_DCT_instrument:
		{
			do
			{
				if ((pNote->pchannel == pChannel)
				&&  (pNote->inst_id == inst_id))
					Note_PreApplyNoteAction(pNote, action);

				pNote = pNote->pnext;
			}
			while (pNote);
		}
		break;
		case Inst_DCT_all:
		{
			do
			{
				if (pNote->pchannel == pChannel)
					Note_PreApplyNoteAction(pNote, action);

				pNote = pNote->pnext;
			}
			while (pNote);
		}
		break;
	}
}

void Notes_ActOnDuplicateNotes
	( NoteHandler* pHNotes
	, uint8_t      action
	, Channel*     pChannel
	, uint8_t      inst_note
	, uint8_t      inst_id
	, uint8_t      smp_id
	, uint8_t      check_type
	)
{
	// loop on virtual channels
	Note* pNote = pHNotes->VirtualNotesPool.pfirst;
	if (!pNote) return;

	switch(check_type)
	{
		case Inst_DCT_note:
		{
			do
			{
				if ((pNote->pchannel == pChannel)
				&&  (pNote->inst_note == inst_note)
				&&  (pNote->inst_id == inst_id))
					Note_ApplyNoteAction(pNote, action);

				pNote = pNote->pnext;
			}
			while (pNote);
		}
		break;
		case Inst_DCT_sample:
		{
			do
			{
				if ((pNote->pchannel == pChannel)
				&&  (pNote->smp_id == smp_id))
					Note_ApplyNoteAction(pNote, action);

				pNote = pNote->pnext;
			}
			while (pNote);
		}
		break;
		case Inst_DCT_instrument:
		{
			do
			{
				if ((pNote->pchannel == pChannel)
				&&  (pNote->inst_id == inst_id))
					Note_ApplyNoteAction(pNote, action);

				pNote = pNote->pnext;
			}
			while (pNote);
		}
		break;
		case Inst_DCT_all:
		{
			do
			{
				if (pNote->pchannel == pChannel)
					Note_ApplyNoteAction(pNote, action);

				pNote = pNote->pnext;
			}
			while (pNote);
		}
		break;
	}
}

void Note_ClearNote(Note* pNote)
{
	pNote->volume = 0;
	pNote->stream.pos = -1;
}

void Note_PreApplyNoteAction(Note* pNote, int action)
{
	if (action >= note_action_continue)
		return;

	switch(action)
	{
		case note_action_cut:
		{
			pNote->new_volume = 0;
		}
		break;
		case note_action_off:
		{
		}
		break;
		case note_action_offxm:
		{
			// if no instrument, cut sample
			if (!pNote->pinstrument)
				pNote->new_volume = 0;
			// if no volume envelope, cut volume (cut note for virtual channel)
			if (!(pNote->envelope_flags & note_envelope_flag_vol))
				pNote->new_volume = 0;
		}
		break;
		case note_action_fade:
		{
			// if no instrument, cut sample
			if (!pNote->pinstrument)
				pNote->new_volume = 0;
		}
		break;
		case note_action_sustain_off:
		{
		}
		break;
	}
}

void Note_ApplyNoteAction(Note* pNote, int action)
{
	if (action >= note_action_continue)
		return;

	switch(action)
	{
		case note_action_cut:
		{
			// Cut sample
			Note_ClearNote(pNote);
		}
		break;
		case note_action_off:
		{
			// If no instrument, do nothing
			if (pNote->pinstrument)
			{
				// Fadeout, only if no envelope or if envelope loop
				if (!(pNote->envelope_flags & note_envelope_flag_vol)
				||  (pNote->pinstrument->VolumeEnvelope.Flags & Envelope_Flag_Loop))
					pNote->inst_flags |= note_inst_flag_fadeout;
				goto sustain_off;
			}
		}
		break;
		case note_action_offxm:
		{
			if (pNote->pinstrument)
			{
				// Fadeout
				pNote->inst_flags |= note_inst_flag_fadeout;
				// If envelope sustain off else cut volume
				if (pNote->envelope_flags & note_envelope_flag_vol)
					goto sustain_off;
				else
				{
					Channel* pChannel = pNote->pchannel;
					// cut volume only
					// In FX: if main note, cut note if virtual note
					if (pChannel->pnote != pNote)
					{
						// Cut sample
						Note_ClearNote(pNote);
					}
					else
					{
						pChannel->note_volume.value.start =
						pChannel->note_volume.value.final = 0;
						goto sustain_off;
					}
				}
			}
			else
			{
				// If no instrument, cut sample
				Note_ClearNote(pNote);
			}
		}
		break;
		case note_action_fade:
		{
			if (pNote->pinstrument)
			{
				// Fadeout
				pNote->inst_flags |= note_inst_flag_fadeout;
			}
			else
			{
				// If no instrument, cut sample
				Note_ClearNote(pNote);
			}
		}
		break;
		case note_action_sustain_off:
		{
sustain_off:
			// Reset sustain flag
			pNote->inst_flags &= ~note_inst_flag_sustain;
			// Fix stream info
			Note_SetStreamInfo(pNote);
		}
		break;
	}
}

// Returns envelope value
int Process_Envelope
	( const NoteHandler* pHNotes
	, const Envelope* pEnv
	, uint32_t* restrict pticks  // Updated on exit
	, uint32_t* restrict ppos    // Updated on exit
	, uint32_t* restrict pflags  // On exit 1 if envelope complete, 0 otherwise
	)
{
	// Check that we did not overflow envelope limits
	// (due to sustain removal for example)
	const uint32_t* pTable = pEnv->pTable;
	uint32_t looppos;
	uint32_t maxpos;
	uint32_t minticks;
	int32_t  minval;
	uint32_t maxticks;
	int32_t  maxval;
	int32_t  val;

	// Get position limit point
	if ((*pflags) & Envelope_Flag_Sustain)
	{
		looppos = pEnv->SustainStart;
		maxpos = pEnv->SustainEnd;
	}
	else if ((*pflags) & Envelope_Flag_Loop)
	{
		looppos = pEnv->LoopStart;
		maxpos = pEnv->LoopEnd;
	}
	else
	{
		looppos =
		maxpos = pEnv->Points - 1;
	}

	// Convert points to pos

	// Read min point, beware unsigned to signed for val
	minval = minticks = pTable[*ppos];
	minval >>= 16;
	minticks = minticks ^ (minval << 16);

	// Special case, within single point loop or end of envelope
	if ((looppos == maxpos) && (*ppos >= looppos))
	{
		*ppos = looppos;
		*pticks = minticks;
		// If no loop, mark envelop as completed
		if ((*pflags) & (Envelope_Flag_Loop|Envelope_Flag_Sustain))
			*pflags = 0;
		else
			*pflags = 1;
		return minval;
	}

	// Read max point, beware unsigned to signed for val
	maxval = maxticks = pTable[(*ppos < maxpos) ? 1 + *ppos : maxpos];
	maxval >>= 16;
	maxticks = maxticks ^ (maxval << 16);

	// Check if current ticks in range
	// cf possible instrument change while playing?
	// Also, IT envelope includes end point
	if (*pticks <= minticks)
		val = minval;
	else if (*pticks >= maxticks)
		val = maxval;
	else
	{
		// Interpol value
		int32_t dticks = maxticks - minticks;
		int32_t dval = minval - maxval;
		int32_t ticks = maxticks - *pticks;
		val = maxval + (dval*ticks)/dticks;
	}

	// Prepare next tick/position
	*pticks += 1;
	bool change = false;
	if (pHNotes->Flags & hnotes_flag_ITenvelopes)
		change = (*pticks > maxticks);
	else
		change = (*pticks >= maxticks);

	if (change)
	{
		*ppos = *ppos + 1;
		if (*ppos < maxpos)
			*pflags = 0;
		else
		{
			*ppos = looppos;
			// Convert points to pos
			minticks = pTable[looppos];
			minticks <<= 16;
			*pticks = minticks >> 16;
			// If no loop, mark envelop as completed
			if ((*pflags) & (Envelope_Flag_Loop|Envelope_Flag_Sustain))
				*pflags = 0;
			else
				*pflags = 1;
		}
	}
	else
	{
		*pflags = 0;
	}

	return val;
}

static void Process_InstrumentVolume(NoteHandler* pHNotes, Note* pNote)
{
	const Instrument* pInst = pNote->pinstrument;

	// Process volume envelope

	// use envelope?
	const Envelope* pEnv = &pInst->VolumeEnvelope;
	if ((pNote->envelope_flags & note_envelope_flag_vol)
	&&  pEnv->Points)
	{
		uint32_t flags = pEnv->Flags;
		uint32_t ticks = pNote->vol_envelope_pos;
		uint32_t pos = ticks & 0xff;
		ticks >>= 8;
		// Reset sustain mode?
		if (!(pNote->inst_flags & note_inst_flag_sustain))
			flags &= ~Envelope_Flag_Sustain;
		// Process envelope
		pNote->envelope_volume = Process_Envelope(pHNotes, pEnv, &ticks, &pos, &flags);
		// Store new position
		pNote->vol_envelope_pos = (ticks << 8) + pos;
		// End of envelope ?
		if (flags)
		{
			// For IT envelopes only, Fadeout
			if (pHNotes->Flags & hnotes_flag_ITenvelopes)
				pNote->inst_flags |= note_inst_flag_fadeout;
			// If final volume is zero cut note
			if (pNote->envelope_volume <= 0)
			{
				if (pHNotes->Flags & hnotes_flag_XMFadeout)
					pNote->envelope_volume = 0;
				else
				{
					Note_ClearNote(pNote);
					return;
				}
			}
 		}
	}
	// Apply even if envelope was disabled
	pNote->volume = (pNote->volume * pNote->envelope_volume) >> 8;

	// Process fadeout volume
	if (pNote->inst_flags & note_inst_flag_fadeout)
	{
		// If fadeout drop to zero, remove note
		pNote->fadeout_volume -= pInst->FadeoutVolume;
		if (pNote->fadeout_volume <= 0)
		{
			if (pHNotes->Flags & hnotes_flag_XMFadeout)
				pNote->fadeout_volume = 0;
			else
			{
				Note_ClearNote(pNote);
				return;
			}
		}
 	}

	uint32_t vol = pNote->fadeout_volume >> 1;
	vol *= (uint32_t) pNote->volume;
	vol >>= 8;
	vol *= (uint32_t) pNote->inst_volume;
	vol >>= 15;
	pNote->volume = vol;
}

static void Process_InstrumentPanning(NoteHandler* pHNotes, Note* pNote)
{
	const Instrument* pInst = pNote->pinstrument;
	int32_t delta;

	// Process panning envelope

	// use envelope?
	const Envelope* pEnv = &pInst->PanningEnvelope;
	if ((pNote->envelope_flags & note_envelope_flag_pan)
	&&  pEnv->Points)
	{
		uint32_t flags = pEnv->Flags;
		uint32_t ticks = pNote->pan_envelope_pos;
		uint32_t pos = ticks & 0xff;
		ticks >>= 8;
		// Reset sustain mode?
		if (!(pNote->inst_flags & note_inst_flag_sustain))
			flags &= ~Envelope_Flag_Sustain;
		// Process envelope
		int32_t val = Process_Envelope(pHNotes, pEnv, &ticks, &pos, &flags) - 128;
		// Store new position
		pNote->pan_envelope_pos = (ticks << 8) + pos;
		// delta *= (distance from left or right border/127)
		int32_t panning = pNote->panning;
		// Treat surround as center
		if (panning > 256)
			panning = 128;
		if (panning < 128)
			delta = panning;
		else
			delta = 256 - panning;
		delta *= val;
		panning += delta >> 7;
		if (panning < 0)
			panning = 0;
		if (panning > 256)
			panning = 256;
		pNote->panning = panning;
	}
}

static void Process_InstrumentPitch(NoteHandler* pHNotes, Note* pNote)
{
	const Instrument* pInst = pNote->pinstrument;
	const Sample* pSample = pNote->psample;

	pNote->realcutoff = pNote->cutoff << 8;

	if (pInst)
	{
		// Process filter envelope

		// use envelope?
		const Envelope* pEnv = &pInst->FilterEnvelope;
		if ((pNote->envelope_flags & note_envelope_flag_filter)
		&&  pEnv->Points)
		{
			uint32_t flags = pEnv->Flags;
			uint32_t ticks = pNote->filter_envelope_pos;
			uint32_t pos = ticks & 0xff;
			ticks >>= 8;
			// Reset sustain mode?
			if (!(pNote->inst_flags & note_inst_flag_sustain))
				flags &= ~Envelope_Flag_Sustain;
			// Process envelope
			int32_t val = Process_Envelope(pHNotes, pEnv, &ticks, &pos, &flags);
			// Store new position
			pNote->filter_envelope_pos = (ticks << 8) + pos;
			// Apply, true = (cutoff * 256) * (env / 256)
			pNote->realcutoff = pNote->cutoff * val;
		}

		// Process pitch envelope

		// use envelope?
		pEnv = &pInst->PitchEnvelope;
		if ((pNote->envelope_flags & note_envelope_flag_pitch)
		&&  pEnv->Points)
		{
			uint32_t flags = pEnv->Flags;
			uint32_t ticks = pNote->pitch_envelope_pos;
			uint32_t pos = ticks & 0xff;
			ticks >>= 8;
			// Reset sustain mode?
			if (!(pNote->inst_flags & note_inst_flag_sustain))
				flags &= ~Envelope_Flag_Sustain;
			// Process envelope
			int32_t val = Process_Envelope(pHNotes, pEnv, &ticks, &pos, &flags);
			// Store new position
			pNote->pitch_envelope_pos = (ticks << 8) + pos;
			// Apply
			pNote->pitch += val;
		}
	}

	// Process auto vibrato
	const AutoVibrato* pAutoVibrato;
	if (pInst && pInst->Vibrato.Depth)
		pAutoVibrato = &pInst->Vibrato;
	else
		pAutoVibrato = &pSample->Vibrato;

	if (pAutoVibrato->Depth)
	{
		uint32_t pos = pNote->smp_vibr_pos;
		uint32_t depth;
		if (pAutoVibrato->Type & vibrato_type_sweep)
		{
			depth = pAutoVibrato->Depth << 8;
			if (pNote->frame_count < pAutoVibrato->Rate)
				depth = (depth * pNote->frame_count) / pAutoVibrato->Rate;
		}
		else
		{
			depth = pNote->smp_vibr_rdepth[0]
			      + (pNote->smp_vibr_rdepth[1] << 8)
			      + (pNote->smp_vibr_rdepth[2] << 16);
		}
		int32_t value = Get_VibratoValue(pAutoVibrato->Type, pos);
		value *= (depth >> 8);
		// round for shift: + half a value
		value += 128;
		pNote->pitch -= value >> 8;
		// New pos
		pos += pAutoVibrato->Speed;
		// New depth
		depth += pAutoVibrato->Rate;
		if (depth > (pAutoVibrato->Depth << 8))
			depth = pAutoVibrato->Depth << 8;
		pNote->smp_vibr_pos = pos;
		pNote->smp_vibr_rdepth[0] = depth;
		pNote->smp_vibr_rdepth[1] = depth >> 8;
		pNote->smp_vibr_rdepth[2] = depth >> 16;
	}
}

static void Note_SetEffectsResults(NoteHandler* pHNotes, Note* pNote)
{
	const Instrument* pInst = pNote->pinstrument;
	const Sample* pSample = pNote->psample;

	if (pNote->stream.pos == -1)
		goto Note_SetNoNote;

	// Step 1 - Read channel info
	pNote->volume = pNote->ch_volume;
	pNote->panning = pNote->ch_panning;
	pNote->pitch = pNote->ch_pitch;

	// Step 2 - Apply Pitch Cmd

	Process_InstrumentPitch(pHNotes, pNote); // no inst test here cf auto vibrato

	// Limit note pitch (or cut note at high pitch)
	if (pNote->pitch < pHNotes->MinPitch)
		pNote->pitch = pHNotes->MinPitch;
	else if (pNote->pitch > pHNotes->MaxPitch)
	{
		if (pHNotes->Flags & hnotes_flag_nocutonhighpitch)
			pNote->pitch = pHNotes->MaxPitch;
		else
			goto Note_SetNoNote;
	}

	// Take finetune into account
	pNote->pitch = Convert_ToneToFrac(pNote->pitch + pNote->finetune);

	if (pInst)
	{

		// Step 3 - Apply Note Volume Cmd

		Process_InstrumentVolume(pHNotes, pNote);

		// Step 4 - Note cannot be cut in the processing that follows
		if (pNote->stream.pos == -1)
			goto Note_SetNoNote;

		// Step 5 - Apply instrument panning commands

		Process_InstrumentPanning(pHNotes, pNote);
	}

	// Step 6 - Set final volume
	pNote->volume = (((pNote->volume * pHNotes->Volume) >> 8) * pSample->ScaleVolume) >> 8;

	// Step 7 - Set frequency
	pNote->frequency = Mul16(pSample->Frequency, pNote->pitch);

	// Step 8 - Set filter parameters
	CITFilter_Params(pNote, pHNotes->MixFrequency);

	return;

Note_SetNoNote:
	Note_ClearNote(pNote);
}

static bool PastNote_IsVanishing(Note* pNote)
{
	// Played or volume before envelope ~0 -> Vanishing note
	if ((pNote->stream.pos == -1)
	||  (pNote->ch_volume <= min_virtual_volume))
		return true;

	// Final volume > ~0 -> Not vanishing note
	if (pNote->volume > min_virtual_volume)
		return false;

	// Final volume ~0 -> Vanishing note except if can climb again due to envelope
	const Instrument* pInst = pNote->pinstrument;
	if (pInst == NULL)
		return true;

	const Envelope* pEnv = &pInst->VolumeEnvelope;
	if ((pNote->envelope_flags & note_envelope_flag_vol)
	&&  pEnv->Points)
	{
		uint32_t flags = pEnv->Flags;
		uint32_t pos;
		uint32_t minpos;
		uint32_t maxpos;

		// Reset sustain mode?
		if (!(pNote->inst_flags & note_inst_flag_sustain))
			flags &= ~Envelope_Flag_Sustain;

		// Get limit points
		pos = pNote->vol_envelope_pos & 0xff;

		if (flags & Envelope_Flag_Sustain)
		{
			minpos = pEnv->SustainStart;
			maxpos = pEnv->SustainEnd;
		}
		else if (flags & Envelope_Flag_Loop)
		{
			minpos = pEnv->LoopStart;
			maxpos = pEnv->LoopEnd;
		}
		else
		{
			// Only check what follows
			minpos = pos + 1;
			maxpos = pEnv->Points - 1;
			if (minpos > maxpos) minpos = maxpos;
		}

		for (uint32_t i = minpos; i <= maxpos; i++)
			if (pNote->envelope_volume < pEnv->pTable[i])
				return false;
	}

	return true;
}

// Decreasing volume, increasing playing time
static int rcompare_notes(const void* pa, const void* pb)
{
	const Note* const* pna = pa;
	const Note* const* pnb = pb;
	int diff;

	diff = (*pnb)->volume - (*pna)->volume;
	if (!diff)
		diff = (*pna)->frame_count - (*pnb)->frame_count;

	return diff;
}

void Notes_MoveNoteToVirtualPool(NoteHandler* pHNotes, Note* pNote)
{
	// Remove note from primary pool
	NoteList_RemoveNote(&pHNotes->PrimaryNotesPool, pNote);

	// Apply note action to channel
	Note_ApplyNoteAction(pNote, pNote->newnoteaction);
	pNote->pchannel->pnote = NULL;

	// Preventive checks
	if (PastNote_IsVanishing(pNote))
	{
		// Insert note in unsued pool
		NoteList_InsertNote(&pHNotes->UnusedNotesPool, pNote);
		pNote->new_volume = 0; // Ramp volume to 0
	}
	else
	{
		// Insert note in virtual pool which must remain sorted on the volume
		// because the quietest virtual notes maybe reused as primary note
		// when the unused pool is empty
		NoteList* pList = &pHNotes->VirtualNotesPool;
		Note* pNext = pList->pfirst;
		Note* pPrev;

		while (pNext && (rcompare_notes(&pNext, &pNote) < 0))
			pNext = pNext->pnext;

		if (pNext)
			pPrev = pNext->pprevious;
		else
			pPrev = pList->plast;

		pNote->pprevious = pPrev;
		pNote->pnext = pNext;

		if (pPrev)
			pPrev->pnext = pNote;
		else
			pList->pfirst = pNote;

		if (pNext)
			pNext->pprevious = pNote;
		else
			pList->plast = pNote;
	}
}

Note* Notes_GetNewNote(NoteHandler* pHNotes, Channel* pChannel)
{
	// Limit notes per channel ?
	if (pHNotes->MaxNotesPerChannel)
	{
		NoteList* pList = &pHNotes->VirtualNotesPool;
		Note* pNote = pList->pfirst;
		int count = 0;

		while (pNote)
		{
			Note* pNext = pNote->pnext;

			if (pNote->pchannel == pChannel)
			{
				count++;
				if (count >= pHNotes->MaxNotesPerChannel)
				{
					// Move Note to unused pool
					NoteList_RemoveNote(pList, pNote);
					NoteList_InsertNote(&pHNotes->UnusedNotesPool, pNote);
					pNote->new_volume = 0; // Ramp volume to 0
				}
			}

			pNote = pNext;
		}
	}

	// Find note in the unused pool or from virtual notes if none there
	NoteList* pList = &pHNotes->UnusedNotesPool;
	Note* pNote = pList->plast;
	if (!pNote)
	{
		pList = &pHNotes->VirtualNotesPool;
		pNote = pList->plast;
	}

	if (pNote)
	{
		// Remove note from its original pool
		NoteList_RemoveNote(pList, pNote);

		// Clear note
		memset(pNote, 0, sizeof(*pNote));

		// Insert note in primary pool
		NoteList_InsertNote(&pHNotes->PrimaryNotesPool, pNote);

		// Reset non-zero values
		pNote->inst_flags = note_inst_default_flags;
		pNote->fadeout_volume = 0x10000;
		pNote->envelope_volume = 0x100;
		pNote->cutoff = 127;

		pChannel->pnote = pNote;
	}

	return pNote;
}

static void Notes_RefreshPools(NoteHandler* pHNotes)
{
	// Remove finished primary notes
	NoteList* pList = &pHNotes->PrimaryNotesPool;
	Note* pNote = pList->pfirst;
	Note* pNext;

	while (pNote)
	{
		pNext = pNote->pnext;

		// Sample played ?
		if (pNote->stream.pos == -1)
		{
			NoteList_RemoveNote(pList, pNote);
			NoteList_InsertNote(&pHNotes->UnusedNotesPool, pNote);
			pNote->new_volume = 0; // Ramp volume to 0
			pNote->pchannel->pnote = NULL;
		}

		pNote = pNext;
	}

	// Remove finished virtual notes
	pList = &pHNotes->VirtualNotesPool;
	pNote = pList->pfirst;

	while (pNote)
	{
		pNext = pNote->pnext;

		// Sample played or volume is ~0 ?
		if (PastNote_IsVanishing(pNote))
		{
			NoteList_RemoveNote(pList, pNote);
			NoteList_InsertNote(&pHNotes->UnusedNotesPool, pNote);
			pNote->new_volume = 0; // Ramp volume to 0
		}

		pNote = pNext;
	}
}

void Notes_Refresh(NoteHandler* pHNotes)
{
	// Refresh note list
	Notes_RefreshPools(pHNotes);

	// Update notes pointers from primary note pool
	Note* pNote = pHNotes->PrimaryNotesPool.pfirst;
	while (pNote)
	{
		pNote->pchannel->pnote = pNote;
		pNote = pNote->pnext;
	}
}

void Notes_BuildActiveNotesList(NoteHandler* pHNotes)
{
	// Process notes in primary pool
	Note* pNote = pHNotes->PrimaryNotesPool.pfirst;
	while (pNote)
	{
		Note_SetEffectsResults(pHNotes, pNote);
		pNote = pNote->pnext;
	}

	// Process notes in virtual pool
	pNote = pHNotes->VirtualNotesPool.pfirst;
	while (pNote)
	{
		Note_SetEffectsResults(pHNotes, pNote);
		pNote = pNote->pnext;
	}

	Notes_RefreshPools(pHNotes);

	// Fill active notes array
	int count = 0;
	Note** ppNote = &pHNotes->ActiveNotesArray[0];

	// Loop on primary pool
	pNote = pHNotes->PrimaryNotesPool.pfirst;
	while (pNote)
	{
		if (!(pNote->inst_flags & note_inst_flag_paused))
		{
			*ppNote++ = pNote;
			count++;
		}
		pNote->frame_count++;
		pNote = pNote->pnext;
	}

	pHNotes->ActiveNotes = count;

	// Keep pointer to virtual notes
	Note** ppVirt = ppNote;

	// Loop on virtual pool
	pNote = pHNotes->VirtualNotesPool.pfirst;
	while (pNote)
	{
		*ppNote++ = pNote;
		pNote->frame_count++;
		count++;
		pNote = pNote->pnext;
	}

	pHNotes->ActiveNotes = count;

	// Sort virtual notes by volume so that quietest ones
	// are dropped first if a primary note gets priority
	if (ppNote != ppVirt)
	{
		qsort(ppVirt, ppNote - ppVirt, sizeof(*ppVirt), rcompare_notes);

		// Rebuild virtual note list from sorted array
		Note** ppEnd = ppNote;
		Note* pPrev = NULL;

		for (ppNote = ppVirt; ppNote < ppEnd; ppNote++)
		{
			pNote = *ppNote;
			pNote->pprevious = pPrev;
			pPrev = pNote;
		}
		pHNotes->VirtualNotesPool.plast = pPrev;

		pPrev = NULL;
		for (ppNote--; ppNote >= ppVirt; ppNote--)
		{
			pNote = *ppNote;
			pNote->pnext = pPrev;
			pPrev = pNote;
		}
		pHNotes->VirtualNotesPool.pfirst = pPrev;
	}
}

void Note_SetStreamInfo(Note* pNote)
{
	const Sample* pSample = pNote->psample;
	int32_t end = pSample->Size;
	uint32_t loop_flags;
	uint32_t loop_start = pSample->LoopStart;
	uint32_t loop_end = pSample->LoopEnd;

	// Set stream pointer and loop limits.
	// If no loop and forward read, both loops limits should be set to size.
	// If no loop and backward read, both loops limits should be set to 0.

	// Set limit for no-loop to start or end of sample depending of direction
	if (pNote->inst_flags & note_inst_flag_invert)
		end = 0;
	// If sustain replace loop limits by sustain ones
	// and use sustain loop flags
	// else use normal ones
	if ((pNote->inst_flags & note_inst_flag_sustain)
	&&  (pSample->Type & Smp_Type_Sustain))
	{
		loop_start = pSample->SustainStart;
		loop_end = pSample->SustainEnd;
		loop_flags = pSample->Type & (Smp_Type_Sustain|Smp_Type_Sustain_Bidi);
		loop_flags >>= 2;
	}
	else
	{
		loop_flags = pSample->Type & (Smp_Type_Loop|Smp_Type_Loop_Bidi);
	}
	// If forced loop flag use forced flags
	if (pNote->inst_flags & note_inst_flag_forceloop)
		loop_flags = pNote->inst_flags & (note_inst_flag_loop|note_inst_flag_loop_bidi);

	// If no loop reset both loop limits to size
	if (!(loop_flags & Smp_Type_Loop))
		loop_start = loop_end = end;
	// Reset both loop limits to size if loop size <= 0
	if (loop_start >= loop_end)
		loop_start = loop_end = end;
	pNote->stream.smp_ptr = pSample->Ptr;
	pNote->stream.smp_lstart = loop_start;
	pNote->stream.smp_lend = loop_end;

	// Set stream flags

	// Keep only current direction
	pNote->stream.flags &= mixstream_flag_isbackward;

	// Set possible directions
	if (pNote->inst_flags & note_inst_flag_invert)
	{
		// Set possible directions: backward, + forward in case of bidi loop
		pNote->stream.flags |= mixstream_flag_backward;
		if (loop_flags & Smp_Type_Loop_Bidi)
			pNote->stream.flags |= mixstream_flag_forward;
	}
	else
	{
		// Set possible directions: forward, + backward in case of bidi loop
		pNote->stream.flags |= mixstream_flag_forward;
		if (loop_flags & Smp_Type_Loop_Bidi)
			pNote->stream.flags |= mixstream_flag_backward;
	}

	// Reset current direction if required
	if (!(pNote->stream.flags & mixstream_flag_backward))
		pNote->stream.flags &= ~mixstream_flag_isbackward;
	if (!(pNote->stream.flags & mixstream_flag_forward))
		pNote->stream.flags |= mixstream_flag_isbackward;

	// Set other flags
	if (pSample->Type & Smp_Type_16bit)
		pNote->stream.flags |= mixstream_flag_16bit;
	// For stereo play only 1 channel
	if (pSample->Type & Smp_Type_Stereo)
		pNote->stream.flags |= mixstream_flag_interlaced;

	// Fix current position
	int32_t pos = pNote->stream.pos;
	int32_t fpos = pNote->stream.fpos;

	// Set check limits (preset above to loop start, loop end)
	if (pNote->stream.flags & mixstream_flag_isbackward)
	{
		loop_end = pSample->Size;
		// Check against loop start/0
		if (pos < loop_start)
		{
			pos = loop_end;
			fpos = 0;
		}
		// Check against size/loop end
		if (pos > loop_end)
		{
			pos = loop_end;
			fpos = 0;
		}
	}
	else
	{
		loop_start = 0;
		// Check against loop start/0
		if (pos < loop_start)
		{
			pos = loop_start;
			fpos = 0;
		}
		// Check against size/loop end
		if (pos > loop_end)
		{
			pos = loop_start; // Move to start
			fpos = 0;
		}
	}
	pNote->stream.pos = pos;
	pNote->stream.fpos = fpos;
}
